from math import tanh
import dbu

def tanhslope(y):
  return 1.0 - y * y

class searchnet:
  def __init__(self, dbname):
    self.db = dbu.db(dbname)

  def __del__(self):
    pass

  def maketables(self):
    self.db.createtables(['hiddennode(create_key)',
                          'wordhidden(fromid, toid, strength)', 
                          'hiddenurl(fromid, toid, strength)'])
    self.db.commit()

  def getstrength(self, table, fromid, toid, default):
    condition = 'fromid=%d and toid=%d' % (fromid, toid)
    res = self.db.queryone(table, 'strength', condition)
    if res == None: return default
    return res[0]

  def get_wordhidden_strength(self, fromid, toid):
    return self.getstrength('wordhidden', fromid, toid, -0.2)

  def get_hiddenurl_strength(self, fromid, toid):
    return self.getstrength('hiddenurl', fromid, toid, 0)

  def set_wordhidden_strength(self, fromid, toid, strength):
    self.setstrength('wordhidden', fromid, toid, strength)

  def set_hiddenurl_strength(self, fromid, toid, strength):
    self.setstrength('hiddenurl', fromid, toid, strength)

  def setstrength(self, table, fromid, toid, strength):
    condition = 'fromid=%d and toid=%d' % (fromid, toid)
    res = self.db.queryone(table, 'rowid', condition)
    if res == None:
      values = '%d,%d,%f' % (fromid, toid, strength)
      self.db.insert(table, 'fromid, toid, strength', values)
    else:
      rowid = res[0]
      self.db.update(table, 'strength=%f' % strength, 'rowid=%d' % res[0])
    #self.db.commit()

  def generatehiddennode(self, wordids, urls):
    if len(wordids) > 3: return None
    create_key = '_'.join(sorted([str(wi) for wi in wordids]))
    condition = 'create_key="%s"' % create_key
    res = self.db.queryone('hiddennode', 'rowid', condition)
    if res == None:
      cur = self.db.insert('hiddennode', 'create_key', '"%s"' % create_key)
      hiddenid = cur.lastrowid

      for wordid in wordids:
        self.set_wordhidden_strength(wordid, hiddenid, 1.0/len(wordids))
      for url in urls:
        self.set_hiddenurl_strength(hiddenid, url, 0.1)
      self.db.commit()
    
  def getrelatedhiddenids(self, wordids, urlids):
    l1 = { }
    for wordid in wordids:
      cur = self.db.query('wordhidden', 'toid', 'fromid=%d' % wordid)
      for row in cur: l1[row[0]] = 1
    for urlid in urlids:
      cur = self.db.query('hiddenurl', 'fromid', 'toid=%d' % urlid)
      for row in cur: l1[row[0]] = 1

    return l1.keys()

  def setupnetwork(self, wordids, urlids):
    self.wordids = wordids
    self.hiddenids = self.getrelatedhiddenids(wordids, urlids)
    self.urlids = urlids

    # allocate memory
    self.ai = [1.0] * len(self.wordids)
    self.ah = [1.0] * len(self.hiddenids)
    self.ao = [1.0] * len(self.urlids)

    self.wi = [[self.get_wordhidden_strength(wordid, hiddenid) for hiddenid in self.hiddenids] for wordid in self.wordids]
    self.wo = [[self.get_hiddenurl_strength(hiddenid, urlid) for urlid in self.urlids] for hiddenid in self.hiddenids]

  def feedforward(self):
    for i in range(len(self.wordids)): self.ai[i] = 1.0

    for j in range(len(self.hiddenids)):
      sum = 0.0
      for i in range(len(self.wordids)):
        sum += self.ai[i] * self.wi[i][j]
      self.ah[j] = tanh(sum)

    for k in range(len(self.urlids)):
      sum = 0.0
      for j in range(len(self.hiddenids)):
        sum += self.ah[j] * self.wo[j][k]
      self.ao[k] = tanh(sum)

    return self.ao[:]

  def getresult(self, wordids, urlids):
    self.setupnetwork(wordids, urlids)
    return self.feedforward()

  def backpropagate(self, targets, N = 0.5):
    output_deltas = [0.0] * len(self.urlids)
    for k in range(len(self.urlids)):
      error = targets[k] - self.ao[k]
      output_deltas[k] = tanhslope(self.ao[k]) * error
    hidden_deltas = [0.0] * len(self.hiddenids)

    for j in range(len(self.hiddenids)):
      error = 0.0
      for k in range(len(self.urlids)):
        error += output_deltas[k] * self.wo[j][k]
        hidden_deltas[j] = tanhslope(self.ah[j]) * error

    for j in range(len(self.hiddenids)):
      for k in range(len(self.urlids)):
        change = output_deltas[k] * self.ah[j]
        self.wo[j][k] = self.wo[j][k] + N * change

    for i in range(len(self.wordids)):
      for j in range(len(self.hiddenids)):
        change = hidden_deltas[j] * self.ai[i]
        self.wi[i][j] = self.wi[i][j] + N * change

  def train(self, wordids, urlids, selectedurl):
    self.generatehiddennode(wordids, urlids)
    self.setupnetwork(wordids, urlids)
    self.feedforward()
    targets = [0.0] * len(urlids)
    targets[urlids.index(selectedurl)] = 1.0
    self.backpropagate(targets)
    self.updatedatabase()

  def updatedatabase(self):
    for i in range(len(self.wordids)):
      for j in range(len(self.hiddenids)):
        self.set_wordhidden_strength(self.wordids[i], self.hiddenids[j], self.wi[i][j])
    for j in range(len(self.hiddenids)):
      for k in range(len(self.urlids)):
        self.set_hiddenurl_strength(self.hiddenids[j], self.urlids[k], self.wo[j][k])
    self.db.commit()
